#!/usr/bin/env python
#
# A little class to aid in the tallying of daily/weekly time information
#
# TODO: rename to TimeTallier


import unittest
import calendar
from datetime import *

class DailyTotal(object):
    def __init__(self, dayOfMonth):
      self.dayOfMonth = dayOfMonth # 1 based!
      self.total = 0.0
      
    def add(self, amount):
      self.total += amount
      
    def __eq__(self, other):
      return self != None and (self.total == other.total) and (self.dayOfMonth == other.dayOfMonth)
    
    def __str__(self):
      return "(day: %2d, amt: %1.2f)" % (self.dayOfMonth, self.total)
    
    def __repr__(self):
      return str(self)

class TimeTallier(object):
    MAX_DAYS = 31
    def __init__(self, year, month):
        self.dayTotals = [DailyTotal(day) for day in range(1, self.MAX_DAYS + 2)]
        self.calendar = calendar.Calendar()
        self.year = year
        self.month = month
        
    def addTimeBlock(self, day, beginTime, endTime):
        self.dayTotals[day].add(self.getHourDifference(beginTime, endTime))
        
    def getHourDifference(self, begin, end):
        # TODO: put date/time differencing into a test (rounding included)
        SECONDS_PER_HOUR = 3600.0
        return (end - begin).seconds / SECONDS_PER_HOUR
        
    def getWeeks(self):
        """
        The days run 0-6, representing Monday-Sunday
        """
        weeks = []
        
        for week in self.calendar.monthdays2calendar(self.year, self.month):
            currentWeek = [None] * 7
            for dayOfMonth, dayOfWeek in week:
                if not dayOfMonth:
                    continue
                currentWeek[dayOfWeek] = self.dayTotals[dayOfMonth - 1]
            weeks.append(currentWeek) 
           
        return weeks
    
    
    # Don't bridge months, run until sunday, then start over
    
    
class TimeTallierTest(unittest.TestCase):
    def assertApproxFloat(self, expected, actual, allowedDiff=0.1):
        self.assertTrue(actual > expected - allowedDiff and actual < expected + allowedDiff)
        
        
    def testGetFirstWeek(self):
        tallier = TimeTallier(2008, 12)
        tallier.addTimeBlock(0, datetime(2008, 12, 1, 0, 1, 0), datetime(2008, 12, 1, 2, 30, 0))
        tallier.addTimeBlock(0, datetime(2008, 12, 1, 0, 1, 0), datetime(2008, 12, 1, 2, 30, 0))
        
        # the spreadsheet is 5 rows of 7 columns always insert at 
        firstWeek = tallier.getWeeks()[0]
        firstDay = firstWeek[0]
        
        self.assertApproxFloat(5.0, firstDay.total)
        
    def testGetSecondWeek(self):
        tallier = TimeTallier(2008, 12)
        
        # 10 hour difference
        tallier.addTimeBlock(7, datetime(2008, 12, 1, 1, 0, 0), datetime(2008, 12, 1, 11, 0, 0))
        
        # Ensure other weeks have only 0.0 sum for all days
        #for i in (0, 2, 3, 4):
        
        self.assertEqual(self.createDailyTotalsForWeek(0), tallier.getWeeks()[0])
        
        # Ensure this week has the proper tally (monday of second week has 10.0 hours)
        secondWeek = self.createDailyTotalsForWeek(1)
        secondWeek[0] = DailyTotal(8)
        secondWeek[0].add(10.0)
        self.assertEqual(secondWeek, tallier.getWeeks()[1])
        
        self.assertEqual(self.createDailyTotalsForWeek(2), tallier.getWeeks()[2])
        self.assertEqual(self.createDailyTotalsForWeek(3), tallier.getWeeks()[3])
        
        lastWeek = self.createDailyTotalsForWeek(4)[:3]
        lastWeek.extend([None] * 4)
        self.assertEqual(lastWeek, tallier.getWeeks()[4])
        
        
    def testGetsDaysOnProperWeekInFeb09(self):
        tallier = TimeTallier(2009, 2)
        firstWeek = tallier.getWeeks()[0]
        firstDayOfMonth = firstWeek[6]
        
        # It should be a Sunday
        self.assertEquals(1, firstDayOfMonth.dayOfMonth)
        
    def createDailyTotalsForWeek(self, weekNumber):
      return [DailyTotal(day + 1) for day in range(weekNumber * 7, weekNumber * 7 + 7)]
      
        
    def testGetHourDifference(self):
        tallier = TimeTallier(2008, 12)
        
        hours = tallier.getHourDifference(datetime(2000, 1, 1, 0, 0, 0), datetime(2000, 1, 1, 5, 0, 0))
        
        self.assertApproxFloat(5, hours)
        
    def testGetHourDifferenceWithPartialHour(self):
        tallier = TimeTallier(2008, 12)
        
        hours = tallier.getHourDifference(datetime(2000, 1, 1, 0, 0, 0), datetime(2000, 1, 1, 2, 30, 0))
        
        # Stoopid assertAlmostEquals doesn't quite do what we need here.
        self.assertApproxFloat(2.5, hours)
        
    def testDailyTotalEqWhenZero(self):
      self.assertEqual(DailyTotal(0), DailyTotal(0))

    def testDailyTotalEqWhenNonZero(self):
      first = DailyTotal(0)
      first.add(2.3)
      
      second = DailyTotal(0)
      second.add(2.3)
      
      self.assertEqual(first, second)
                
if __name__ == "__main__":
	unittest.main()

